package org.zoomdev.zoom.dao.builder.impl;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.zoomdev.zoom.dao.Dao;
import org.zoomdev.zoom.dao.Databases;
import org.zoomdev.zoom.dao.builder.BuilderDriver;
import org.zoomdev.zoom.dao.builder.DatabaseBuilder;
import org.zoomdev.zoom.dao.builder.driver.H2Builder;
import org.zoomdev.zoom.dao.builder.driver.MysqlBuilder;
import org.zoomdev.zoom.dao.structs.ColumnMeta;

import java.sql.Types;
import java.util.ArrayList;
import java.util.List;

public class ZoomDatabaseBuilder implements DatabaseBuilder {


    private static final Log log = LogFactory.getLog(ZoomDatabaseBuilder.class);
    private final Dao dao;
    private final List<TableBuildInfo> tables = new ArrayList<TableBuildInfo>();
    private final BuilderDriver driver;
    private final List<BuildInfo> buildInfo = new ArrayList<BuildInfo>();
    private TableBuildInfo tableBuildInfo;
    private ColumnBuildInfo columnMeta;

    public ZoomDatabaseBuilder(Dao dao) {
        this.dao = dao;
        this.driver = create(dao);
    }

    private BuilderDriver create(Dao dao) {
        if (Databases.MYSQL.equals(dao.productName())) {
            return new MysqlBuilder(dao.sqlDriver());
        }
        if (Databases.H2.equals(dao.productName())) {
            return new H2Builder(dao.sqlDriver());
        }
        return null;
    }

    @Override
    public DatabaseBuilder dropIfExists(String table) {
        buildInfo.add(new DropTableIfExists(table));
        return this;
    }

    @Override
    public DatabaseBuilder createIfNotExists(String table) {
        createTable(table);
        tableBuildInfo.createWhenNotExists = true;

        return this;
    }

    @Override
    public DatabaseBuilder comment(String comment) {
        if (columnMeta != null) {
            columnMeta.setComment(comment);

        } else if (tableBuildInfo != null) {
            tableBuildInfo.comment = comment;
        }

        return this;
    }

    @Override
    public DatabaseBuilder createTable(String table) {
        tableBuildInfo = new TableBuildInfo();
        tableBuildInfo.name = table;
        tables.add(tableBuildInfo);
        buildInfo.add(new CreateTable(tableBuildInfo));

        columnMeta = null;
        return this;
    }

    @Override
    public DatabaseBuilder add(String column) {

        columnMeta = new ColumnBuildInfo();
        tableBuildInfo.columns.add(columnMeta);
        columnMeta.setNullable(true);
        columnMeta.setName(column);

        return this;
    }

    @Override
    public DatabaseBuilder modify(String table, String column) {
        return this;
    }

    @Override
    public DatabaseBuilder string(int len) {
        columnMeta.setType(Types.VARCHAR);
        columnMeta.setMaxLength(len);
        return this;
    }

    @Override
    public DatabaseBuilder charString(int len) {
        columnMeta.setType(Types.CHAR);
        return this;
    }

    @Override
    public DatabaseBuilder nstring(int len) {
        columnMeta.setType(Types.NVARCHAR);
        columnMeta.setMaxLength(len);
        return this;
    }

    @Override
    public DatabaseBuilder smallInt() {
        columnMeta.setType(Types.SMALLINT);
        columnMeta.setUnsigned(false);
        return this;
    }

    @Override
    public DatabaseBuilder smallIntUnsigned() {
        columnMeta.setType(Types.SMALLINT);
        columnMeta.setUnsigned(true);
        return this;
    }

    @Override
    public DatabaseBuilder text() {
        columnMeta.setType(Types.CLOB);
        return this;
    }

    @Override
    public DatabaseBuilder decimal(int n, int m) {
        columnMeta.setType(Types.DECIMAL);
        columnMeta.setDecimal(n, m);
        return this;
    }

    @Override
    public DatabaseBuilder timestamp() {
        columnMeta.setType(Types.TIMESTAMP);
        return this;
    }

    @Override
    public DatabaseBuilder date() {
        columnMeta.setType(Types.DATE);
        return this;
    }

    @Override
    public DatabaseBuilder integer() {
        columnMeta.setType(Types.INTEGER);
        return this;
    }

    @Override
    public DatabaseBuilder integerUnsigned() {
        columnMeta.setType(Types.INTEGER);
        columnMeta.setUnsigned(true);
        return this;
    }

    @Override
    public DatabaseBuilder bigInt() {
        columnMeta.setType(Types.BIGINT);
        return this;
    }

    @Override
    public DatabaseBuilder bigIntUnsigned() {
        columnMeta.setType(Types.BIGINT);
        columnMeta.setUnsigned(true);
        return this;
    }

    @Override
    public DatabaseBuilder tinyInt() {
        columnMeta.setType(Types.TINYINT);
        return this;
    }

    @Override
    public DatabaseBuilder tinyIntUnsigned() {
        columnMeta.setType(Types.TINYINT);
        columnMeta.setUnsigned(true);
        return this;
    }

    @Override
    public DatabaseBuilder clob() {
        columnMeta.setType(Types.CLOB);
        return this;
    }

    @Override
    public DatabaseBuilder number() {
        columnMeta.setType(Types.NUMERIC);
        return this;
    }


    @Override
    public DatabaseBuilder notNull() {
        columnMeta.setNullable(false);
        return this;
    }

    @Override
    public DatabaseBuilder keyPrimary() {
        columnMeta.setKeyType(ColumnMeta.KeyType.PRIMARY);
        return this;
    }

    @Override
    public DatabaseBuilder autoIncement() {
        columnMeta.setAuto(true);
        return this;
    }

    @Override
    public DatabaseBuilder keyUnique() {
        columnMeta.setKeyType(ColumnMeta.KeyType.UNIQUE);
        return this;
    }

    @Override
    public DatabaseBuilder keyIndex() {
        columnMeta.setKeyType(ColumnMeta.KeyType.INDEX);
        return this;
    }

    @Override
    public DatabaseBuilder engine(String engine) {
        this.tableBuildInfo.engine = engine;
        return this;
    }

    @Override
    public String buildSql() {
        List<String> list = new ArrayList<String>();


        for (BuildInfo buildInfo : buildInfo) {
            buildInfo.build(list);
        }


        StringBuilder sb = new StringBuilder();

        for (String sql : list) {
            sb.append(sql);
            if (!sql.endsWith(";")) {
                sb.append(";");
            }
            sb.append("\n");
        }

        return sb.toString();

    }

    @Override
    public void build() {
        final List<String> list = new ArrayList<>();
        for (BuildInfo buildInfo : buildInfo) {
            list.clear();
            buildInfo.build(list);
            for (String sql : list) {
                dao.executeUpdate(sql);
            }
        }
    }


    @Override
    public DatabaseBuilder defaultValue(Object value) {
        columnMeta.setDefaultValue(value);
        return this;
    }

    @Override
    public DatabaseBuilder blob() {
        columnMeta.setType(Types.BLOB);
        return this;
    }

    @Override
    public DatabaseBuilder mediumBlob() {
        columnMeta.setType(Types.BLOB);
        columnMeta.setSubType(ColumnBuildInfo.MEDIUM);
        return this;
    }

    @Override
    public DatabaseBuilder defaultFunction(String value) {
        columnMeta.setDefaultValue(new FunctionValue(value));
        return this;
    }

    private static abstract class BuildInfo {
        abstract void build(List<String> sqls);
    }

    private class DropTableIfExists extends BuildInfo {

        private final String table;

        public DropTableIfExists(String table) {
            this.table = table;
        }

        @Override
        void build(List<String> sqls) {

            sqls.add(driver.buildDropIfExists(table));


        }
    }

    private class CreateTable extends BuildInfo {

        private final TableBuildInfo table;


        public CreateTable(TableBuildInfo table) {
            this.table = table;
        }

        @Override
        void build(List<String> sqls) {
            driver.buildTable(table, sqls);
        }
    }


}
